package com.xuzz.xrpc.spring.context.processors;

import com.xuzz.xrpc.common.annotation.Service;
import com.xuzz.xrpc.spring.context.BootstrapApplicationListener;
import com.xuzz.xrpc.spring.context.ServiceBean;
import com.xuzz.xrpc.spring.context.annotation.RPCClassPathBeanDefinitionScanner;
import org.springframework.beans.BeansException;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.config.*;
import org.springframework.beans.factory.support.*;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.AnnotationBeanNameGenerator;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import java.lang.annotation.Annotation;
import java.util.*;
import java.util.logging.Logger;

import static org.springframework.beans.factory.support.BeanDefinitionBuilder.rootBeanDefinition;
import static org.springframework.context.annotation.AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR;
import static org.springframework.core.annotation.AnnotatedElementUtils.findMergedAnnotation;
import static org.springframework.core.annotation.AnnotationUtils.getAnnotationAttributes;
import static org.springframework.util.ClassUtils.resolveClassName;

/**
 * @description:
 * @author: xuzz
 * @create: 2020-02-21 18:04
 */
public class ServiceAnnotationBeanPostProcessor implements BeanDefinitionRegistryPostProcessor, EnvironmentAware,
        ResourceLoaderAware, BeanClassLoaderAware {

    Set<String> scanPackages;

    private Environment environment;

    private ResourceLoader resourceLoader;

    private ClassLoader classLoader;


    public ServiceAnnotationBeanPostProcessor(String... packagesToScan) {
        this(Arrays.asList(packagesToScan));
    }

    public ServiceAnnotationBeanPostProcessor(Collection<String> packagesToScan) {
        this(new LinkedHashSet<>(packagesToScan));
    }

    public ServiceAnnotationBeanPostProcessor(Set<String> packagesToScan) {
        this.scanPackages = packagesToScan;
    }


    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {

        //查找@Service注解，生成ServiceBean。
        //在bootstrap中，根据ServiceBean 注册服务到注册中心

        Set<String> resolvedPackagesToScan = resolvePackagesToScan(scanPackages);
        if (!CollectionUtils.isEmpty(resolvedPackagesToScan)) {
 //           System.out.println("在ServiceAnnotationBeanPostProcessor中，开始扫描"+resolvedPackagesToScan);
            registerServiceBeans(resolvedPackagesToScan, registry);
        } else {
            //这里后续要根据日志决定是否输出日志
            System.out.println("packagesToScan is empty , ServiceBean registry will be ignored!");
        }
    }

    private Set<String> resolvePackagesToScan(Set<String> packagesToScan) {
        Set<String> resolvedPackagesToScan = new LinkedHashSet<String>(packagesToScan.size());
        for (String packageToScan : packagesToScan) {
            if (StringUtils.hasText(packageToScan)) {
                String resolvedPackageToScan = environment.resolvePlaceholders(packageToScan.trim());
                resolvedPackagesToScan.add(resolvedPackageToScan);
            }
        }
        return resolvedPackagesToScan;
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

    }

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }

    @Override
    public void setBeanClassLoader(ClassLoader classLoader) {
        this.classLoader = classLoader;
    }

    private void registerServiceBeans(Set<String> packagesToScan, BeanDefinitionRegistry registry) {

        RPCClassPathBeanDefinitionScanner scanner =
                new RPCClassPathBeanDefinitionScanner(registry, environment, resourceLoader);

        BeanNameGenerator beanNameGenerator = resolveBeanNameGenerator(registry);

        scanner.setBeanNameGenerator(beanNameGenerator);

        scanner.addIncludeFilter(new AnnotationTypeFilter(Service.class));

        for (String packageToScan : packagesToScan) {

            // Registers @Service Bean first
            scanner.scan(packageToScan);
            // Finds all BeanDefinitionHolders of @Service whether @ComponentScan scans or not.
            Set<BeanDefinitionHolder> beanDefinitionHolders =
                    findServiceBeanDefinitionHolders(scanner, packageToScan, registry, beanNameGenerator);

            if (!CollectionUtils.isEmpty(beanDefinitionHolders)) {

                for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) {
                    registerServiceBean(beanDefinitionHolder, registry, scanner);
                }

            } else {
                System.out.println("No Spring Bean annotating @Service was found under package["
                        + packageToScan + "]");
            }

        }

    }

    private BeanNameGenerator resolveBeanNameGenerator(BeanDefinitionRegistry registry) {
        BeanNameGenerator beanNameGenerator = null;
        if (registry instanceof SingletonBeanRegistry) {
            SingletonBeanRegistry singletonBeanRegistry = SingletonBeanRegistry.class.cast(registry);
            beanNameGenerator = (BeanNameGenerator) singletonBeanRegistry.getSingleton(CONFIGURATION_BEAN_NAME_GENERATOR);
        }
        if (beanNameGenerator == null) {
            beanNameGenerator = new AnnotationBeanNameGenerator();

        }
        return beanNameGenerator;

    }

    private Set<BeanDefinitionHolder> findServiceBeanDefinitionHolders(
            ClassPathBeanDefinitionScanner scanner, String packageToScan, BeanDefinitionRegistry registry,
            BeanNameGenerator beanNameGenerator) {

        Set<BeanDefinition> beanDefinitions = scanner.findCandidateComponents(packageToScan);

        Set<BeanDefinitionHolder> beanDefinitionHolders = new LinkedHashSet<>(beanDefinitions.size());

        for (BeanDefinition beanDefinition : beanDefinitions) {

            String beanName = beanNameGenerator.generateBeanName(beanDefinition, registry);
            BeanDefinitionHolder beanDefinitionHolder = new BeanDefinitionHolder(beanDefinition, beanName);
            beanDefinitionHolders.add(beanDefinitionHolder);

        }

        return beanDefinitionHolders;

    }

    private void registerServiceBean(BeanDefinitionHolder beanDefinitionHolder, BeanDefinitionRegistry registry,
                                     RPCClassPathBeanDefinitionScanner scanner) {

        Class<?> beanClass = resolveClass(beanDefinitionHolder);

        Annotation service = findServiceAnnotation(beanClass);

        AnnotationAttributes serviceAnnotationAttributes = getAnnotationAttributes(service, false, false);

        String[] annotationAttrInterfaces = serviceAnnotationAttributes.getStringArray("interfaces");
        if(beanClass.getInterfaces().length>1 && annotationAttrInterfaces.length<1)
        {
            System.out.println("服务实现的接口不止一个，请在interfaces字段中指定具体的服务接口");
        }
        //如果注解中指定了接口，那么使用注解的
        if(annotationAttrInterfaces.length>0)
        {
            for(String annotationAttrInterface: annotationAttrInterfaces)
            {
                AbstractBeanDefinition serviceBeanDefinition =
                        buildServiceBeanDefinition(
                                annotationAttrInterface,
                                beanClass.getName()
                        );
                registry.registerBeanDefinition(annotationAttrInterface, serviceBeanDefinition);
            }
        }
        else {
            Class<?>[] intefaces = beanClass.getInterfaces();

            if (null != intefaces && intefaces.length == 1) {
                String beanName = intefaces[0].getName();
                AbstractBeanDefinition serviceBeanDefinition =
                        buildServiceBeanDefinition(
                                beanName,
                                beanClass.getName()
                        );
                registry.registerBeanDefinition(beanName, serviceBeanDefinition);
            }
        }
    }


    private Annotation findServiceAnnotation(Class<?> beanClass) {
        Annotation service = findMergedAnnotation(beanClass, Service.class);
        return service;
    }

    private Class<?> resolveClass(BeanDefinitionHolder beanDefinitionHolder) {

        BeanDefinition beanDefinition = beanDefinitionHolder.getBeanDefinition();

        return resolveClass(beanDefinition);

    }

    private Class<?> resolveClass(BeanDefinition beanDefinition) {

        String beanClassName = beanDefinition.getBeanClassName();

        return resolveClassName(beanClassName, classLoader);

    }

    private AbstractBeanDefinition buildServiceBeanDefinition(String intefaceName,String serviceImplClassName) {

        BeanDefinitionBuilder builder = rootBeanDefinition(ServiceBean.class);

        AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();

        beanDefinition.getPropertyValues().addPropertyValue("interfaceName",intefaceName);
                beanDefinition.getPropertyValues().addPropertyValue("className",serviceImplClassName);

        return builder.getBeanDefinition();

    }
}
